package com.bcdbook.activiti.web;

import com.bcdbook.activiti.convertor.ProcessInstance2SimpleProcessInstanceConvertor;
import com.bcdbook.activiti.form.ProcessStartForm;
import com.bcdbook.activiti.security.SecurityUtil;
import com.bcdbook.activiti.support.SimpleProcessInstance;
import com.bcdbook.activiti.utils.ActivitiUtils;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricActivityInstanceQuery;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.image.ProcessDiagramGenerator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 流程图的 Controller
 *
 * @author summer
 * @version V1.0.0-RELEASE
 * DateTime 2019-03-22 12:46
 */
@RestController
@RequestMapping("/process")
@Slf4j
@Validated
public class ProcessController {

    /**
     * 注入 Security 的工具类
     */
    @Resource
    private SecurityUtil securityUtil;
    /**
     * 注入历史 service
     */
    @Resource
    private HistoryService historyService;

    /**
     * 注入资源 service
     */
    @Resource
    private RepositoryService repositoryService;

    /**
     * 注入流程图生成器
     */
    @Resource
    private ProcessDiagramGenerator processDiagramGenerator;

    /**
     * 注入运行时 service
     */
    @Resource
    private RuntimeService runtimeService;

    /**
     * 开启流程的方法
     *
     * @param processStartForm 流程开启的 Form
     * @return boolean
     * Author summer
     * Version V1.0.0-RELEASE
     * DateTime 2019-03-22 10:53
     */
    @PostMapping("/start")
    public String startProcess(@NotNull(message = "开启流程的信息不能为空") @Valid
                                   @RequestBody ProcessStartForm processStartForm){

        // 获取在线用户
        UserDetails userDetails = securityUtil.getActiveUserDetails();
        // 设置了此数据以后, activiti 会自动设置 start id 等信息
        // TODO 此操作之后要放到认证完成后的方法上
        Authentication.setAuthenticatedUserId(userDetails.getUsername());

        // 创建流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processStartForm.getProcessKey(),
                processStartForm.getBusinessKey(),
                processStartForm.getProcessValue());

        // 设置流程的名称为自定义名称
        runtimeService.setProcessInstanceName(processInstance.getId(), processStartForm.getProcessName());

        log.info("Process Start, ProcessInstance: {}, processValue: {}",
                processInstance, processStartForm.getProcessValue());

        // 返回流程实例的 id
        return processInstance.getId();
    }

    /**
     * 获取所有的流程集合
     *
     * @return java.util.List<com.bcdbook.activiti.support.SimpleProcessInstance>
     * Author summer
     * Version V1.0.0-RELEASE
     * DateTime 2019-03-23 16:53
     */
    @GetMapping
    public List<SimpleProcessInstance> listProcessInstance(){

        List<ProcessInstance> processInstanceList = runtimeService
                .createProcessInstanceQuery()
                .list();

        return ProcessInstance2SimpleProcessInstanceConvertor.convert(processInstanceList);
    }



    /**
     * 根据流程实例的 id 查询流程对象
     *
     * @param processInstanceId 流程实例的 id
     * @return com.bcdbook.activiti.support.SimpleProcessInstance
     * Author summer
     * Version V1.0.0-RELEASE
     * DateTime 2019-03-23 16:53
     */
    @GetMapping("/{processInstanceId}")
    public SimpleProcessInstance getProcessInstanceById(@PathVariable(name = "processInstanceId")
                                                                    String processInstanceId){

        ProcessInstance processInstance = runtimeService
                .createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();

        return ProcessInstance2SimpleProcessInstanceConvertor.convert(processInstance);
    }

    /**
     * 根据用户名, 获取用户所拥有的流程集合
     *
     * @param username 用户名
     * @return java.util.List<com.bcdbook.activiti.support.SimpleProcessInstance>
     * Author summer
     * Version V1.0.0-RELEASE
     * DateTime 2019-03-23 16:53
     */
    @GetMapping("/user")
    public List<SimpleProcessInstance> listProcessInstanceByUsername(String username){

        List<ProcessInstance> processInstanceList = runtimeService
                .createProcessInstanceQuery()
                .startedBy(username)
                .list();

        return ProcessInstance2SimpleProcessInstanceConvertor.convert(processInstanceList);
    }

    /**
     * 获取流程图图片
     *
     * @param processInstanceId 流程实例 id
     * @param response 返回对象
     * @return void
     * Author summer
     * Version V1.0.0-RELEASE
     * DateTime 2019-03-23 20:07
     */
    @RequestMapping(value="/images")
    public void showImg(@RequestParam("processInstanceId") String processInstanceId,
                        HttpServletResponse response) {
        /*
         * 参数校验
         */
        log.info("查看完整流程图！流程实例ID:{}", processInstanceId);
        if(StringUtils.isBlank(processInstanceId)) {
            return;
        }

        /*
         *  获取流程实例
         */
        HistoricProcessInstance processInstance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        if(processInstance == null) {
            log.error("流程实例ID:{}没查询到流程实例！", processInstanceId);
            return;
        }

        // 根据流程对象获取流程对象模型
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());


        /*
         *  查看已执行的节点集合
         *  获取流程历史中已执行节点，并按照节点在流程中执行先后顺序排序
         */
        // 构造历史流程查询
        HistoricActivityInstanceQuery historyInstanceQuery = historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(processInstance.getId());

        // 查询历史节点
        List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery
                .orderByHistoricActivityInstanceStartTime()
                .asc()
                .list();

        if(historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) {
            log.info("流程实例ID:{}没有历史节点信息！", processInstanceId);
            outputImg(response, bpmnModel, null, null);
            return;
        }
        // 已执行的节点ID集合(将historicActivityInstanceList中元素的activityId字段取出封装到executedActivityIdList)
        List<String> executedActivityIdList = historicActivityInstanceList
                .stream()
                .map(HistoricActivityInstance::getActivityId)
                .collect(Collectors.toList());

        /*
         * 获取流程走过的线
         */
        // 获取流程定义
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                .getDeployedProcessDefinition(processInstance.getProcessDefinitionId());

        List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);

        response.setHeader("X-Frame-Options", "SAMEORIGIN");
        response.setContentType("text/html");
        /*
         * 输出图像，并设置高亮
         */
        outputImg(response, bpmnModel, flowIds, executedActivityIdList);
    }

    /**
     * 输出图像
     *
     * @author summer
     * @date 2019-01-04 16:15
     * @param response 响应实体
     * @param bpmnModel 图像对象
     * @param flowIds 已执行的线集合
     * @param executedActivityIdList 已执行的节点ID集合
     * @return void
     * @version V1.0.0-RELEASE
     */
    private void outputImg(HttpServletResponse response,
                           BpmnModel bpmnModel,
                           List<String> flowIds,
                           List<String> executedActivityIdList) {
        InputStream imageStream = null;

        try {
            //processDiagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds,"宋体","宋体",null,1.0);
            //单独返回流程图，不高亮显示
            imageStream = processDiagramGenerator.generateDiagram(
                    bpmnModel,
                    executedActivityIdList,
                    flowIds,
                    "宋体",
                    "微软雅黑",
                    "黑体",
                    true,
                    "easyspring.png");

            // 输出资源内容到相应对象
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, 1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
            response.getOutputStream().flush();
        }catch(Exception e) {
            log.error("流程图输出异常！", e);
        } finally { // 流关闭
            if (imageStream != null) {
                try {
                    imageStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
